// Initial File - Paul Jack

// QT Includes
#include <qapplication.h>
#include <qdatetime.h>
#include <qlist.h>
#include <qtimer.h>

#include "asyncmgrbase.h"
#include "messagequeue.h"
#include "workerthreads.h"
#include "workerinterface.h"



/*!
 * AsyncMgrBase::AsyncMgrBase 
 *
 * Description: 
 * Base for the replaceable Async device.
 *
 * @param localName is of type QString 
 * @param remoteName is of type QString 
 * @param identification is of type QString 
 *
 * @returns None
 */
AsyncMgrBase::AsyncMgrBase( QString localName, QString remoteName, QString identification, quint32 bufferSize ) :
mLocalName( localName ),
mRemoteName( remoteName ),
mAttached( false ),
mBufferSize(bufferSize),
mNeedNewBuffer(true)
{
    mIdentification = identification;
    mDevice = &mNullDevice;

    mByteArray = NULL;
    mStringSize = 0;

    mBulkSupported = true;
    mRunning = false;
    mEnableMsCallback = false;

    mpWriteQueue = new MessageQueue<AsyncMgrBase> ( this );
    mpReadQueue = new MessageQueue<AsyncMgrBase> ( this );

    mpReadBulkThreadInterface = new ReadBulkThread( this );
    mReadBulkThread.setWorkerInterface( mpReadBulkThreadInterface );
    mReadBulkThread.setName( "ReadBulkThread" );
    mpReadThreadInterface = new ReadThread( this );
    mReadThread.setWorkerInterface( mpReadThreadInterface );
    mReadThread.setName( "ReadThread" );
    mpWriteThreadInterface = new WriteThread( this );
    mWriteThread.setWorkerInterface( mpWriteThreadInterface );
    mWriteThread.setName( "WriteThread" );
}




/*!
 * AsyncMgrBase::~AsyncMgrBase 
 *
 * Description: 
 * Destructor
 *
 * @returns None.
 */
AsyncMgrBase::~AsyncMgrBase()
{
    // disconnect ();
}




/*!
 * AsyncMgrBase::connect 
 *
 * Description: 
 *
 *
 * @returns bool 
 */
bool AsyncMgrBase::connect()
{
    disconnect ();

    mBytesSent = 0;
    mBytesReceived = 0;

    // initialize the write buffer
    memset ( mBuffer.data (), 0, mBuffer.size () );

    // We don't have any way to reconnect right now. :(
    return ( false );
}




/*!
 * AsyncMgrBase::disconnect 
 *
 * Description: 
 * stop the threads
 *
 * @returns void 
 */
void AsyncMgrBase::disconnect()
{
    if ( isConnected () )
    {

        // if port is open, then stop the thread and close the port
        stop ();

        mDevice->mhDevice = 0;
    }
}




/*!
 * AsyncMgrBase::forwardData 
 *
 * Description: 
 * Move the data into our buffer
 *
 * @param data is of type char 
 *
 * @returns inline void 
 */
inline void AsyncMgrBase::forwardData( char data )
{
    if ( mStringSize >= ( int ) mBuffer.size () - 2 )
    {
        mBuffer.resize ( mBuffer.size () * 2 );
    }

    mBuffer[mStringSize++] = data;
    mBuffer[mStringSize + 2] = '\0';
}




/*!
 * AsyncMgrBase::getDeviceName 
 *
 * Description: 
 * returns the name of the device
 *
 *
 * @returns QString 
 */
QString AsyncMgrBase::getDeviceName()
{
    QString tmp;

    tmp.sprintf ( "Product Id[%04x], Vendor Id[%04x]", mDevice->mProductId, mDevice->mVendorId );

    return ( tmp );
};






/*!
 * AsyncMgrBase::isConnected 
 *
 * Description: 
 *
 *
 * @returns bool 
 */
bool AsyncMgrBase::isConnected()
{
    return ( mDevice->isConnected () );
}




/*!
 * AsyncMgrBase::stop 
 *
 * Description: 
 * Stop the read, write and bulk threads
 *
 *
 * @returns void 
 */
void AsyncMgrBase::stop()
{
    if ( isRunning () )
    {
        bool    resultRead;
        bool    resultWrite;
        bool    resultReadBulk = true;

        qDebug ( "AsyncMgrBase::stop()  Attempting stop." );
        mpWriteQueue->resetBegin ();
        mpReadQueue->resetBegin ();

        mWriteThread.stop ();
        mReadThread.stop ();

        if ( isBulkDataSupported () )
        {
            mReadBulkThread.stop ();
            resultReadBulk = mReadBulkThread.wait ( 3000 );
        }

        resultRead = mReadThread.wait ( 30000 );
        resultWrite = mWriteThread.wait ( 3000 );

        if ( resultRead == false || resultWrite == false || resultReadBulk == false )
        {
            if ( resultRead == false )
            {
                qWarning ( "AsyncMgrBase::readThread did not exit. AsyncMgrBase ""is now in an unknown state." );
            }

            if ( resultWrite == false )
            {
                qWarning ( "AsyncMgrBase::writeThread did not exit. AsyncMgrBase ""is now in an unknown state." );
            }

            if ( resultReadBulk == false )
            {
                qWarning ( "AsyncMgrBase::readBulkThread did not exit. AsyncMgrBase ""is now in an unknown state." );
            }
        }

        mpReadQueue->resetEnd ();
        mpWriteQueue->resetEnd ();

        mRunning = false;
    }
}




/*!
 * AsyncMgrBase::stopBulk 
 *
 * Description: 
 * shut off just the bulk input
 *
 * @returns bool 
 */
bool AsyncMgrBase::stopBulk()
{
    bool    resultReadBulk = false;
    if ( isBulkDataSupported () )
    {
        mReadBulkThread.stop ();
        resultReadBulk = mReadBulkThread.wait ( 3000 );
        mNeedNewBuffer = true;
        if(mByteArray)
        {
            delete mByteArray;
            mByteArray = NULL;
        }
    }

    return ( resultReadBulk );
}




/*!
 * AsyncMgrBase::isRunning 
 *
 * Description: 
 * is any of the threads running?
 *
 * @returns bool 
 */
bool AsyncMgrBase::isRunning()
{

    //    bool readThread = mReadThread.isRunning();
    //    bool writeThread = mWriteThread.isRunning();
    //    bool bulkThread = mReadBulkThread.isRunning();
    return ( mReadThread.isRunning () || mWriteThread.isRunning () || mReadBulkThread.isRunning () || mRunning );
}




/*!
 * AsyncMgrBase::readLoop 
 *
 * Description: 
 * Continuous read loop.
 * The data is read and posted to the GUI message queue.
 * The read is from the "Read" message queue  which is running
 * in a different thread.  That thread continously attempts to
 * read from the device and places what it reads into its  
 * input queue for reading here at the leasure of the GUI
 *
 *
 * @returns bool 
 */
bool AsyncMgrBase::readLoop()
{
    bool result = false;

    QByteArray     qValue;

    mReadString = "";

    // Are callbacks are available?
    bool    callbackEnabled = isMsCallbackEnabled () == true;

    // If callbacks are available and data is available to post
    if ( callbackEnabled )
    {
        qValue = qValue = mpReadQueue->pop ( 0 );
        if(!qValue.isEmpty())
        {
            // get a copy of the data and delete the old data.
            mReadString = qValue;
        }
    }

    // if callbacks aren't available and we failed to read a string
    else if ( mDevice->read ( mReadString ) == false )
    {
        qDebug ( "AsyncMgrBase::readLoop() failed" );

        // if we returned false then it would end the thread!
        return false;
    }

    // if we have successfully read back a non-zero length string
    if ( mReadString.length () > 0 )
    {
        mpReadQueue->push ( mReadString );
        mReadString.clear();
    }

    return ( true );
}




/*!
 * AsyncMgrBase::readBulkLoop 
 *
 * Description: 
 * Read the bulk data out of the bulk queue
 * Data is continously read in the bulk thread
 * and we just read it here from the GUI
 *
 * @returns bool 
 */
bool AsyncMgrBase::readBulkLoop()
{
    bool results = false;
    QByteArray data;
    static int  timeoutCount = 0;

    if(!isRunning())
    {
        return results;
    }

    //mByteArray = new QByteArray;
    data.resize ( mBufferSize );

    if ( mDevice->readBulk ( data ) == false )
    {
        // Find out what the error was
        quint32 errorCode = mDevice->getReadBulkErrorCode();
        if ( timeoutCount++ == MAX_BULK_RETRIES )
        {
            timeoutCount = 0;
        }

        // Emit that we did not successfully receive a buffer
        emit    receivingBulk( false, data );
        results = false;

    }
    else
    {
        // Emit that a successful buffer has come in.
        emit    receivingBulk( true, data );
        results = true;
    }

    //delete mByteArray;
    // For Bulk, we always return true to keep the thread running;
    return ( results );
}




/*!
 * AsyncMgrBase::writeLoop 
 *
 * Description: 
 * Pull a message from the write queue and pass it along to the device
 * for writing.
 *
 * @returns bool 
 */
bool AsyncMgrBase::writeLoop()
{
    bool result = false;
    // Wait for a message to appear
    // This call will block until something is put into
    // the write queue.
    QByteArray     str = mpWriteQueue->pop ();

    // No message to send!
    // This probably is an error or the queue is in a reset condition!
    if ( str.isEmpty() )
    {
        result = true;
        return ( result );
    }

    if ( str.count () )
    {
        if ( !mDevice->write ( str ) )
        {
            qDebug ( "AsyncMgrBase::writeLoop: Error sending message %s", *str );
            result = false;
        }
        else
        {
            result = true;
        }
    }

    //delete str;

    return ( result );
}




/*!
 * AsyncMgrBase::write 
 *
 * Description: 
 * Push a QByteArray string onto the write queue.
 *
 * @param str is of type QByteArray* 
 *
 * @returns bool 
 */
//bool AsyncMgrBase::write( QByteArray str )
//{
//    return ( mpWriteQueue->push ( str ) );
//}




/*!
 * AsyncMgrBase::read 
 *
 * Description: 
 * Read from the message queue with timeouts
 *
 * @param str is of type QByteArray& 
 * @param timeout is of type int 
 *
 * @returns bool 
 */
bool AsyncMgrBase::read( QByteArray& str, int timeout )
{
    bool    result;
    mMutex.lock ();

    bool    mWasEnabled = mEnableMsCallback;
    mEnableMsCallback = false;
    mMutex.unlock ();

    QByteArray     tmp = mpReadQueue->pop ( timeout );

    if ( tmp.isEmpty() )
    {
        return ( false );
    }

    if ( tmp.count () != 0 )
    {
        result = true;
        str = tmp.data ();
        // get rid of the original message.
    }
    else
    {
        result = false;
    }

    if ( mWasEnabled != mEnableMsCallback )
    {
        mMutex.lock ();
        mEnableMsCallback = mWasEnabled;
        mMutex.unlock ();
    }

    return ( result );
}




/*!
 * AsyncMgrBase::start 
 *
 * Description: 
 * Start the threads
 *
 * @returns void 
 */
void AsyncMgrBase::start()
{
    if ( isRunning () == false )
    {
        if ( !mReadThread.isRunning () )
        {
            mReadThread.start ();
        }

        if ( !mWriteThread.isRunning () )
        {
            mWriteThread.start ();
        }

        mRunning = true;
    }
}




/*!
 * AsyncMgrBase::startBulk 
 *
 * Description: 
 * Start the bulk thread
 *
 * @returns void 
 */
void AsyncMgrBase::startBulk()
{
    if ( isBulkDataSupported () )
    {
        if ( !mReadBulkThread.isRunning () )
        {
            mReadBulkThread.start ();
            mReadBulkThread.setPriority(QThread::TimeCriticalPriority);
        }
    }
}




/*!
 * AsyncMgrBase::getProduct 
 *
 * Description: 
 * Return the product Name
 *
 * @returns QString 
 */
QString AsyncMgrBase::getProduct()
{
    return ( mIdProduct );
};





/*!
 * AsyncMgrBase::isBulkDataSupported 
 *
 * Description: 
 *
 *
 * @returns bool 
 */
bool AsyncMgrBase::isBulkDataSupported()
{
    return ( mBulkSupported );
};






/*!
 * AsyncMgrBase::getVendor 
 *
 * Description: 
 *
 *
 * @returns QString 
 */
QString AsyncMgrBase::getVendor()
{
    return ( mIdVendor );
};






/*!
 * AsyncMgrBase::write 
 *
 * Description: 
 * Write directly to the device.
 *
 * @param str is of type const char* 
 * @param size is of type int 
 *
 * @returns bool 
 */
bool AsyncMgrBase::write( const char* str, int size )
{
    bool            error;

    QByteArray     tmp(str, size);// = new QByteArray ( str, size );
    error = write ( tmp );
    return ( error );
};






/*!
 * AsyncMgrBase::write 
 *
 * Description: 
 * Write directly to the device.
 *
 * @param str is of type QString 
 *
 * @returns bool 
 */
bool AsyncMgrBase::write( QString str )
{
    bool            error;

    // Make a copy because the real write needs a pointer!
    //QByteArray     tmp(str);// = new QByteArray ( str.toAscii () );
    error = write ( str.toAscii() );
    return ( error );
}




/*!
 * AsyncMgrBase::write 
 *
 * Description: 
 * Write directly to the device.
 *
 * @param str is of type const QByteArray& 
 *
 * @returns bool 
 */
bool AsyncMgrBase::write( const QByteArray& str )
{
    return ( mpWriteQueue->push ( str ) );
};
